﻿// Courtesy Steve Marx
namespace Bowlus.FrontEnd.RequestRouting.BlobLeaseUtil
{
    using System;
    using System.Diagnostics;
    using System.Net;

    using Bowlus.Common;

    using Microsoft.WindowsAzure.StorageClient;
    using Microsoft.WindowsAzure.StorageClient.Protocol;

    public class BlobLease : IBlobLease
    {
        private readonly CloudBlob leaseBlob;

        private string leaseId;

        public BlobLease(string leaseBlobName)
        {
            var blobClient = Settings.AzureStorageAccount.CreateCloudBlobClient();
            blobClient.ReadAheadInBytes = 0;
            var container = blobClient.GetContainerReference("leases");
            container.CreateIfNotExist();

            this.leaseBlob = container.GetBlobReference(leaseBlobName);
            if (!this.BlobExists())
            {
                var newBlob = container.GetPageBlobReference(leaseBlobName);
                newBlob.Create(0);
                this.leaseBlob = newBlob;
            }
        }

        public BlobLeaseStatus LeaseStatus { get; private set; }

        public void AcquireLease()
        {
            var creds = this.leaseBlob.ServiceClient.Credentials;
            var transformedUri = new Uri(creds.TransformUri(this.leaseBlob.Uri.ToString()));
            var req = BlobRequest.Lease(transformedUri, 90, LeaseAction.Acquire, null);
            this.leaseBlob.ServiceClient.Credentials.SignRequest(req);
            try
            {
                using (var response = req.GetResponse())
                {
                    this.leaseId = response.Headers["x-ms-lease-id"];
                    this.LeaseStatus = BlobLeaseStatus.Acquired;
                }
            }
            catch (WebException webException)
            {
                var response = webException.Response as HttpWebResponse;
                Debug.Assert(response != null, "response != null");
                if (response.StatusCode == HttpStatusCode.Conflict)
                {
                    this.leaseId = string.Empty;
                    this.LeaseStatus = BlobLeaseStatus.Failed;
                }
            }
        }

        private static void DoLeaseOperation(CloudBlob blob, string leaseId, LeaseAction action)
        {
            var creds = blob.ServiceClient.Credentials;
            var transformedUri = new Uri(creds.TransformUri(blob.Uri.ToString()));
            var req = BlobRequest.Lease(transformedUri, 90, action, leaseId);
            creds.SignRequest(req);
            try
            {
                req.GetResponse().Close();
            }
            catch (WebException)
            {
                // silently eat the exception
            }
        }

        public void ReleaseLease()
        {
            try
            {
                DoLeaseOperation(this.leaseBlob, this.leaseId, LeaseAction.Release);
            }
            catch (WebException webException)
            {
                var response = webException.Response as HttpWebResponse;
                Debug.Assert(response != null, "response != null");
                if (response.StatusCode != HttpStatusCode.Conflict)
                {
                    return;
                }
                this.leaseId = string.Empty;
                this.LeaseStatus = BlobLeaseStatus.Failed;
            }
        }

        public void RenewLease()
        {
            try
            {
                DoLeaseOperation(this.leaseBlob, this.leaseId, LeaseAction.Renew);
            }
            catch (WebException webException)
            {
                var response = webException.Response as HttpWebResponse;
                Debug.Assert(response != null, "response != null");
                if (response.StatusCode != HttpStatusCode.Conflict)
                {
                    return;
                }
                this.leaseId = string.Empty;
                this.LeaseStatus = BlobLeaseStatus.Failed;
            }

        }

        public void BreakLease()
        {
            try
            {
                DoLeaseOperation(this.leaseBlob, null, LeaseAction.Break);
            }
            catch (WebException webException)
            {
                var response = webException.Response as HttpWebResponse;
                Debug.Assert(response != null, "response != null");
                if (response.StatusCode != HttpStatusCode.Conflict)
                {
                    return;
                }
                this.leaseId = string.Empty;
                this.LeaseStatus = BlobLeaseStatus.Failed;
            }

        }

        private bool BlobExists()
        {
            try
            {
                this.leaseBlob.FetchAttributes();
                return true;
            }
            catch (StorageClientException e)
            {
                if (e.ErrorCode == StorageErrorCode.ResourceNotFound)
                {
                    return false;
                }

                throw;
            }
        }
    }
}